
const path = require('path')
const fs = require('fs')

const contract = require('../contract/index')
const comment = require('../comment/index')
const _ = require('../constant/index')
/**
 * swagger Document
 */
let DOCUMENT
const FUNCTIONBUNDLER = []
/**
 * 构建Document
 * @controller {tagName} {description} 接受swaggerDoc扫描，并命名该controller名字，默认为文件名
 * @deprecated 未完成接口
 * @summary {content} 接口标题
 * @description {content} 接口描述
 * @router {method} {routerkey} 接口请求地址
 * @request {position} {type} {name} {description} 请求体
 * @response {http_status} {type} {description} 响应体
 * @consume {consume} 例 @consume application/json
 * @produce {produce} 例 @consume application/json
 * @ignore 实验性功能，自动生成路由时(需遵循一定规则)，跳过指定非 API function扫描
 */
function buildDocument(app) {
  // config
  const swagger = app.config.swaggerdoc

  const securitys = []
  let tag_path = {
    tags: [],
    paths: {}
  }

  // 允许使用验证
  if (swagger.enableSecurity) {
    // 获取定义的安全验证名称
    for (const security in swagger.securityDefinitions) {
      securitys.push(security)
    }
  }

  // 遍历contract,组装swagger.definitions
  const definitions = contract.getDefinitions(app)

  const filepath = path.join(app.config.baseDir, swagger.dirScanner)

  // 递归获取 tags&paths
  tag_path = getTag_Path(filepath, securitys, swagger, definitions)

  // build document
  DOCUMENT = {
    host: '',
    swagger: _.SWAGGERVERSION,
    basePath: swagger.basePath,
    info: swagger.apiInfo,
    schemes: swagger.schemes,
    tags: tag_path.tags,
    paths: tag_path.paths,
    securityDefinitions: swagger.securityDefinitions,
    definitions
  }

  return DOCUMENT
}

function getTag_Path(fileDir, securitys, swagger, definitions) {
  // 已存在tag集合
  const tagNames = []
  let tags = []
  const paths = {}

  const names = fs.readdirSync(fileDir)
  for (const name of names) {
    const filepath = path.join(fileDir, name)
    const stat = fs.statSync(filepath)

    if (stat.isDirectory()) {
      const subPath = getTag_Path(filepath, securitys, swagger, definitions)
      // 合并子目录的扫描结果
      tags = tags.concat(subPath.tags)
      Object.assign(paths, subPath.paths)
      continue
    }

    if (stat.isFile() && ['.js', '.ts'].indexOf(path.extname(name)) !== -1) {
      const extname = path.extname(name)
      if (extname === '.ts') {
        const jsFile = name.replace('.ts', '.js')
        if (names.indexOf(jsFile) >= 0) {
          continue
        }
      }

      const blocks = comment.generateCommentBlocks(filepath)

      // 如果第一个注释块不包含@controller不对该文件注释解析
      if (blocks.length === 0 || !hasController(blocks[0])) continue

      // 当前注释块集合的所属tag-group, 并添加至swagger.tags中
      const controller = comment.getComment(blocks[0], _.CONTROLLER)[0]
      let tagName = controller[1] ? controller[1] : name.split(/\.(js|ts)/)[0]
      if (tagNames.includes(tagName)) {
        tagName = tagName + '_' + tagNames.length
      }
      tagNames.push(tagName)

      tags.push({ name: tagName, description: controller[2] ? controller[2] : '' })

      // 获取所有的有效方法
      const func = generateAPIFunc(filepath)
      const bundler = {
        filePath: filepath,
        routers: []
      }

      const routerlist = []
      for (let i = 1; i < blocks.length; i++) {
        if (isIgnore(blocks[i])) continue

        const direct = `${filepath.split(/\.(js|ts)/)[0].split('app')[1].substr(1)}`
        // 解析路由
        const routers = comment.getComment(blocks[i], _.ROUTER)
        if (routers) {
          const path_method = {}
          path_method.tags = [tagName]
          path_method.summary = generateSummary(blocks[i])
          path_method.description = generateDescription(blocks[i])
          path_method.operationId = `${direct.replace(path.sep, '-')}-${func[i - 1]}`
          path_method.consumes = generateConsumes(blocks[i], swagger)
          path_method.produces = generateProduces(blocks[i], swagger)
          path_method.parameters = generateRequest(blocks[i], routers, definitions)
          path_method.security = generateSecurity(blocks[i], securitys, swagger)
          path_method.responses = generateResponse(blocks[i], routers, definitions)
          path_method.deprecated = isDeprecated(blocks[i])

          if (!routerlist.includes(routers[0][2])) {
            paths[routers[0][2]] = {}
          }

          routerlist.push(routers[0][2])
          paths[routers[0][2]][routers[0][1].toLowerCase()] = path_method

          // 绑定route和function
          const contractName = getContractInBody(blocks[i], definitions)
          const router = {
            method: routers[0][1].toLowerCase(),
            route: routers[0][2],
            func: func[i - 1],
            ruleName: contractName
          }
          bundler.routers.push(router)
        }
      }
      FUNCTIONBUNDLER.push(bundler)
    }
  }

  return {
    tags,
    paths
  }
}

/**
 * 判断是否包含@Controller标签
 * @param {String} block 注释块
 * @return {Boolean} 是否包含@Controller标签
 */
function hasController(block) {
  return block.indexOf('@Controller') > -1 || block.indexOf('@controller') > -1
}

/**
 * 获取controller的方法， 按定义顺序
 * @param {String} filepath controller文件地址
 */
function generateAPIFunc(filepath) {
  let func = []
  let obj = require(filepath)
  if (path.extname(filepath) === '.ts') {
    obj = obj.default
  }

  const instance = obj.prototype || obj
  func = Object.getOwnPropertyNames(instance).map(key => {
    return key
  })

  if (func[0] === 'constructor') {
    func.shift()
  }

  return func
}

/**
 * 是否跳过该方法
 * @param {string} block 注释块
 */
function isIgnore(block) {
  return block.indexOf('@Ignore') > -1 || block.indexOf('@ignore') > -1
}

/**
 * 是否无效接口
 * @param {string} block 注释块
 */
function isDeprecated(block) {
  return block.indexOf('@Deprecated') > -1 || block.indexOf('@deprecated') > -1
}

/**
 * 解析安全验证
 * @param {String} block 注释块
 * @param {Array} securitys 设定的安全验证名称
 * @param {Object} swagger swagger配置
 */
function generateSecurity(block, securitys, swagger) {
  const securityDoc = []
  for (const security of securitys) {
    if (block.indexOf(`@${security}`) > -1) {
      const securityItem = {}
      if (swagger.securityDefinitions[security].type === 'apiKey') {
        securityItem[security] = []
        securityItem[security].push(swagger.securityDefinitions[security])
      }
      if (swagger.securityDefinitions[security].type === 'oauth2') {
        securityItem[security] = []
        Object.keys(swagger.securityDefinitions[security].scopes).forEach(i => {
          securityItem[security].push(i)
        })
      }
      securityDoc.push(securityItem)
    }
  }
  return securityDoc
}

/**
 * 获取api标题
 * @param {String} block 注释块
 */
function generateSummary(block) {
  let summary = ''
  const summarys = comment.getComment(block, _.SUMMARY)
  if (summarys) {
    let m = 1
    while (summarys[0][m]) {
      summary = summary + summarys[0][m] + ' '
      m++
    }
  }
  return summary
}
/**
 * 获取api接口描述
 * @param {String} block 注释块
 */
function generateDescription(block) {
  let description = ''
  const descriptions = comment.getComment(block.replace(/^\s+\*\s+^/gm, '\n').replace(/^\s+\*\s*/gm, ''), _.DESCRIPTION)
  if (descriptions) {
    let m = 1
    while (descriptions[0][m]) {
      description = description + descriptions[0][m] + ' '
      m++
    }
  }
  return description
}

/**
 * 获取请求的produces
 * @param {String} block comment block
 * @param {Object} swagger config of swagger
 */
function generateProduces(block, swagger) {
  let produces = []
  const produceComments = comment.getComment(block, _.PRODUCE)
  if (produceComments) {
    for (const item of produceComments) {
      for (const key in item) {
        if (Number(key) === 0) continue
        produces.push(item[key])
      }
    }
  } else {
    produces = swagger.produces
  }
  return produces
}

/**
 * 获取请求的consumes
 * @param {String} block comment block
 * @param {Object} swagger config of swagger
 */
function generateConsumes(block, swagger) {
  let consumes = []
  const consumeComments = comment.getComment(block, _.CONSUME)
  if (consumeComments) {
    for (const item of consumeComments) {
      for (const key in item) {
        if (Number(key) === 0) continue
        consumes.push(item[key])
      }
    }
  } else {
    consumes = swagger.consumes
  }
  return consumes
}

/**
 * 获取请求参数
 * @param {String} block 注释块
 * @param {Array} routers 路由列表
 * @param {Object} definitions contract信息
 */
function generateRequest(block, routers, definitions) {
  const parameters = []
  const requests = comment.getComment(block, _.REQUEST)
  if (requests) {
    for (const request of requests) {
      const parameter = generateParameters(request, routers, definitions)
      parameters.push(parameter)
    }
  }
  return parameters
}

/**
 * 获取request in body
 * @param {String} block comment
 * @param {Object} definitions contract定义
 */
function getContractInBody(block, definitions) {
  const requests = comment.getComment(block, _.REQUEST)
  if (requests) {
    for (const request of requests) {
      if (request[1] === 'body' && definitions.hasOwnProperty(request[2])) {
        return request[2]
      }
    }
  }
}

/**
 * 获取响应参数
 * @param {String} block 注释块
 * @param {Array} routers 路由列表
 * @param {Object} definitions contract信息
 */
function generateResponse(block, routers, definitions) {
  const responseDoc = {}
  const responses = comment.getComment(block, _.RESPONSE)
  if (responses) {
    for (const response of responses) {
      const res = {}
      const schema = {}

      if (response[2] && response[2] !== 'none') {
        if (!definitions.hasOwnProperty(response[2])) {
          throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of response parameter does not exit`)
        }
        schema.$ref = `#/definitions/${response[2]}`
        res.schema = schema
      }

      res.description = ''
      if (response[3]) {
        let m = 3
        while (response[m]) {
          res.description = res.description + response[m] + ' '
          m++
        }
      }

      responseDoc[response[1]] = res
    }
  } else {
    responseDoc.default = { description: 'successful operation' }
  }

  // 若需要鉴权 则存在返回401状态码情况
  if (block.indexOf('@Authorization') !== -1) {
    responseDoc[401] = { description: 'Unauthorized' }
  }
  // 若需要传入请求参数 则存在返回状态码400情况
  if (block.indexOf('@request') !== -1) {
    responseDoc[400] = { description: 'INVALID REQUEST' }
  }
  // 一定存在 500的情况
  responseDoc[500] = { description: 'INTERNAL SERVER ERROR' }

  return responseDoc
}
/**
 * 获取请求参数
 * @param {String} request 包含@Request的注释行,以空格分割的得到的数组
 * @param {Array} routers 路由信息
 * @param {Object} definitions contract信息
 */
function generateParameters(request, routers, definitions) {
  const parameter = {}

  parameter.in = request[1]
  if (parameter.in.toLowerCase() === 'body' && !_.itemType.includes(request[2])) {
    const schema = {}
    if (!request[2].startsWith('array')) {
      if (!definitions.hasOwnProperty(request[2])) {
        throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of request parameter does not exit`)
      }
      schema.$ref = `#/definitions/${request[2]}`
    } else {
      schema.type = 'array'
      const ObjectType = ['boolean', 'integer', 'number', 'string']
      const items = {}
      const itemsType = request[2].substring(6, request[2].length - 1)
      if (ObjectType.includes(itemsType)) {
        items.type = itemsType
      } else {
        if (!definitions.hasOwnProperty(itemsType)) {
          throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of request parameter does not exit`)
        }
        items.$ref = `#/definitions/${itemsType}`
      }
      schema.items = items
    }

    parameter.schema = schema
  } else if (parameter.in.toLowerCase() === 'query' && request[2].startsWith('array')) {
    parameter.type = 'array'

    const ObjectType = ['boolean', 'integer', 'number', 'string']
    const items = {}
    const itemsType = request[2].substring(6)
    if (!ObjectType.includes(itemsType)) {
      throw new Error(`[egg-swagger-doc] error at ${routers[0][1].toLowerCase()}:${routers[0][2]} ,the type of request parameter does not exit`)
    }

    items.type = itemsType

    parameter.items = items
    parameter.collectionFormat = 'multi'
  } else {
    parameter.type = request[2]
  }

  if (request[3]) {
    parameter.name = request[3].replace('*', '')

    parameter.required = false
    if (request[3].indexOf('*') > -1 || parameter.in === 'path') {
      parameter.required = true
    }
  }

  if (parameter.in.toLowerCase() === 'body') {
    parameter.name = 'body'
    parameter.required = true
  }

  parameter.description = ''

  let i = 4
  while (request[i]) {
    if (request[i].indexOf('eg:') > -1) {
      const example = request[i].replace('eg:', '')

      if (request[2].startsWith('array')) {
        parameter.items.example = example
      } else {
        parameter.example = example
      }
    }
    if (request[i].indexOf('enum:') > -1) {
      const enums = request[i].replace('enum:', '').split(',')

      if (request[2].startsWith('array')) {
        parameter.items.enum = enums
      } else {
        parameter.enum = enums
      }
    } else {
      parameter.description = parameter.description + request[i] + ' '
    }
    i++
  }

  return parameter
}

module.exports = {

  documentInit: app => {
    if (!DOCUMENT) {
      buildDocument(app)
    }

    return DOCUMENT
  },

  getFuncBundler: app => {
    if (!FUNCTIONBUNDLER) {
      buildDocument(app)
    }

    return FUNCTIONBUNDLER
  }
}
